﻿/*
 * RFID Attendance Program
 * 
 * This program connects to an RFID reader via serial COM port.
 * It is used to track students who have showed up to class,
 * left class, and at what times they have done so. It also
 * keeps a list of which students never showed up.
 * 
 * Original Author: Nick Kravchuk
 * Current Author: Charles Rosaaen
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;
using System.Collections;
using ExcelLibrary.SpreadSheet;


namespace RFID
{
   partial class Form1 : Form
   {

       /***************************************************************************
        * Globals
        ***************************************************************************/

       // Declare and initialize data field
       string data = String.Empty; 

       // Create a Serial Port object
       SerialPort sp = new SerialPort();

       // Create ArrayList to hold students
       List<Student> students = new List<Student>();

       // Delegate methods for thread safety
       delegate void SetTextCallback(string text);
       delegate void AddListItem(ListView list, ListViewItem item);
       delegate void ClearLists_d();
       delegate void StartTimer_d();

       // Boolean to determine whether the program is currently connected to a port
       bool isConnected = false;

       // Boolean to turn debug features on or off. Primarily, turns on or off whether
       // to write a new students.txt file when called. (true == no new file written)
       bool debug = false;

       // Mode defines what mode the system is in.
       // 0 == disconnected
       // 1 == connected, attendance
       // 2 == connected, add / remove
       int mode = 0;

       // Boolean used to make the program wait 200 milliseconds between scans,
       // will hopefully fix hard-to-replicate bug where one card is read as another
       bool shouldWait = false;

       int waitloop = 0;



       /***************************************************************************
        * METHODS
        ***************************************************************************/



       // Constructor
       public Form1()
       {
           InitializeComponent();

           //initialize connection variables
           sp.BaudRate = 115200;
           sp.DataBits = 8;
           sp.Parity = Parity.None;
           sp.StopBits = StopBits.One;
           sp.Handshake = Handshake.None;

           // Build list of active ports in cmboPortList
           foreach (string port in SerialPort.GetPortNames())
               cmboPortList.Items.Add(port);

           // Attach handler for received data
           sp.DataReceived += new SerialDataReceivedEventHandler(SerialReceive);

           // Build student list
           BuildStudents();

           // Add all students to absent list
           foreach (Student s in students)
               lstAbsent.Items.Add(s.getLVI_A());
  
      }



       // Handler for received data. Seems to run in different thread,
       // so any attempts to modifiy form controls require an invoke
       public void SerialReceive(object sender, SerialDataReceivedEventArgs e)
       {
           if (!shouldWait)
           {
               // Put current data from serial port into data variable
               data = sp.ReadExisting();
               // Process data to remove random line breaks
               data = data.Replace("\r", "");
               data = data.Replace("\n", "");
               // Invokes txtDecode for thread safety reasons
               txtDecode.Invoke(new EventHandler(delegate
               {
                   // Appends current data to txtbox
                   txtDecode.Font = new Font(txtDecode.Font, FontStyle.Bold);
                   txtDecode.AppendText(data);
                   txtDecode.ScrollToCaret();
                   // Sticks txtDecode's text into label, supposedly cleans out line breaks
                   //lblInfo.Text = txtDecode.Text;
               }));

               // If we have a full RFID...
               if (txtDecode.Text.Length > 23)
               {
                   Console.WriteLine("timer starting");
                   shouldWait = true;
                   StartTimer();
                   if (mode == 1) // Attendance mode
                       Log(txtDecode.Text);
                   else if (mode == 2) // Add Remove mode
                       LogAddRemove(txtDecode.Text);
                   else
                   {
                       MessageBox.Show("Data was received, but the mode was unrecognized. Mode was: " + mode, "Error", MessageBoxButtons.OK);
                       this.Close();
                   }
                   Clear();
               }
           }
           else
           {
               waitloop++;
               Console.WriteLine("currently waiting " + waitloop);
           }
       }



       // Logs the coming or going of students
      private void Log(string data)
      {
          // Clean out line breaks yet again
          data = data.Replace("\r", "");

          // Get the index of the student if the RFID is valid, otherwise return -1
          int i = MatchStudent(data);

          // If the RFID matches a student's ID...
          if (i != -1)
          {
              if (students[i].getInTime() == Student.initTime)
                  students[i].setInTime(DateTime.Now);
              else if (students[i].getOutTime() == Student.initTime)
                  students[i].setOutTime(DateTime.Now);

              SetText_lblInfo(students[i].getName() + "\n Time: " + DateTime.Now);
          }
          else // Otherwise, the RFID doesn't match any student. Say so.
              SetText_lblInfo("Student not found.");

          RefreshLists();
      }



       // Puts scanned RFIDs into txtAddID
      private void LogAddRemove(string data)
      {
          txtAddID.Invoke(new EventHandler(delegate
          {
              txtAddID.Text = data;
          }));
      }



       // On close
      private void Form1_FormClosing(object sender, FormClosingEventArgs e)
      {
         sp.Close();
      }



       // Clear out various buffers to prepare for new RFID
      private void Clear()
      {
          txtDecode.Invoke(new EventHandler(delegate
          {
              txtDecode.Clear();
          }));
          data = String.Empty;
          sp.DiscardOutBuffer();
          sp.DiscardInBuffer();
      }



       // Connect or disconnect to / from attendance mode
      private void btnConnect_Click(object sender, EventArgs e)
      {
          ConnectDisconnect();
          if (isConnected)
          {
              gbxAddRemove.Enabled = false;
              mode = 1;
          }
          else
          {
              gbxAddRemove.Enabled = true;
              mode = 0;
          }
      }



       // Attempts to connect to and disconnect from the currently selected port
      private void ConnectDisconnect()
      {
          // If we are not currently connected, attempt to connect.
          if (!isConnected)
          {
              try
              {
                  sp.Open();
                  lblConnectInfo.Text = "Connected Successfully";
                  btnConnect.Text = "Disconnect";
                  isConnected = true;
              }
              catch (IOException excep)
              {
                  lblConnectInfo.Text = excep.Message;
              }
          }
          else // Otherwise, disconnect.
          {
              sp.Close();
              lblConnectInfo.Text = "Disconnected";
              btnConnect.Text = "Connect";
              isConnected = false;
          }
      }



    // changes COM Port
      private void cmboPortList_SelectedIndexChanged(object sender, EventArgs e)
      {
          try
          {
              sp.PortName = cmboPortList.SelectedItem.ToString();
              if (!btnConnect.Enabled)
              {
                  btnConnect.Enabled = true;
                  btnAddRemoveSetup.Enabled = true;
                  lblPickPortNotice.Visible = false;
              }
          }
          catch (InvalidOperationException)
          {
              MessageBox.Show("Disconnect before changing the port.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
          }
      }



       // Builds the array of students from the file "students.txt"
       private void BuildStudents()
       {
           if (!File.Exists("students.txt"))
           {
               Console.WriteLine("creating students.txt");
               StreamWriter sw = new StreamWriter("students.txt", false);
               sw.WriteLine(0);
               sw.Close();
           }
           StreamReader sr = new StreamReader("students.txt");
           int studentCount = Convert.ToInt32(sr.ReadLine());

           for (int x = 0; x < studentCount; x++)
           {
               sr.ReadLine();
               string name = sr.ReadLine();
               string id = sr.ReadLine();
               students.Add(new Student(id, name));
           }
           sr.Close();
       }



       // Checks if a passed RFID matches one of the students in the students array.
       // If it does, return the index of the matching student. If it doesn't,
       // return -1.
       private int MatchStudent(string id)
       {
           int index = -1;
           foreach (Student s in students)
           {
               index++;
               if (id == s.getID())
               {
                   return index;
               }
           }
           return -1;
       }



       // Sets up the lists for adding and removing students.
       private void btnAddClearSetup_Click(object sender, EventArgs e)
       {
           DialogResult ans;

           // Prompts if sure when not connected
           if (!isConnected)
               ans = MessageBox.Show("This will reset the Absent, In, and Out lists. It will also attempt to connect on the currently selected port. Continue?", "Prompt", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
           else
               ans = DialogResult.Yes;

           // If the user is sure (or we are disconnecting)
           if (ans == DialogResult.Yes)
           {
               // Clear out lists, rebuild Absent list
               ClearLists();
               foreach (Student s in students)
                   lstAbsent.Items.Add(s.getLVI_A());

               // Reset the in and out times of all students
               foreach (Student s in students)
               {
                   s.setInTime(Student.initTime);
                   s.setOutTime(Student.initTime);
               }

               // Connect or disconnect, modify form controls accordingly
               ConnectDisconnect();
               if (isConnected)
               {
                   btnAddRemoveSetup.Text = "Disconnect";
                   btnConnect.Enabled = false;
                   btnAdd.Enabled = true;
                   btnRemove.Enabled = true;
                   lstAbsent.Enabled = true;
                   txtAddID.Enabled = true;
                   txtAddName.Enabled = true;
                   lblAbsent.Text = "Students";
                   mode = 2;
               }
               else 
               {
                   btnAddRemoveSetup.Text = "Set Up...";
                   btnConnect.Enabled = true;
                   btnAdd.Enabled = false;
                   btnRemove.Enabled = false;
                   lstAbsent.Enabled = false;
                   txtAddID.Enabled = false;
                   txtAddName.Enabled = false;
                   lblAbsent.Text = "Absent";
                   mode = 0;
               }
           }
       }



       // Removes the selected student, if one is selected.
       private void btnRemove_Click(object sender, EventArgs e)
       {
           if (lstAbsent.SelectedItems.Count > 0)
           {
               int index = -1;

               foreach (Student s in students)
                   if (lstAbsent.SelectedItems[0] == s.getLVI_A())
                       index = lstAbsent.SelectedIndices[0];

               if (index != -1)
               {
                   students.RemoveAt(index);
                   lstAbsent.Items.Clear();
                   foreach (Student s in students)
                       lstAbsent.Items.Add(s.getLVI_A());
                   WriteNewStudentFile();
                   txtAddName.Text = null;
                   txtAddID.Text = null;
               }
               else
                   MessageBox.Show("Select a student from the Students list first.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
           }
       }



       // Sticks the info of the selected student into the textboxes for adding and removing
       private void lstAbsent_SelectedIndexChanged(object sender, EventArgs e)
       {
           if (lstAbsent.SelectedItems.Count > 0)
           {
               int index = -1;

               foreach (Student s in students)
                   if (lstAbsent.SelectedItems[0] == s.getLVI_A())
                       index = lstAbsent.SelectedIndices[0];

               //MessageBox.Show(Convert.ToString(index));
               if (index != -1)
               {
                   txtAddName.Text = students[index].getName();
                   txtAddID.Text = students[index].getID();
               }
           }
       }



       // Writes new version of "students.txt", called when a student is added or removed
       private void WriteNewStudentFile()
       {
           if (!debug)
           {
               StreamWriter sw = new StreamWriter("students.txt", false);
               sw.WriteLine(students.Count());
               foreach (Student s in students)
               {
                   sw.WriteLine();
                   sw.WriteLine(s.getName());
                   sw.WriteLine(s.getID());
               }
               sw.Close();
           }
       }



       // Adds a new student, so long as the name and ID don't already exist
       private void btnAdd_Click(object sender, EventArgs e)
       {
           string prospName = txtAddName.Text,
                  prospID = txtAddID.Text;

           // If name and ID are not empty...
           if (prospName != "" && prospID != "")
           {
               bool nameConflict = false,
                    idConflict = false;

               // Check for conflicts
               foreach (Student s in students)
               {
                   if (prospName == s.getName())
                       nameConflict = true;
                   if (prospID == s.getID())
                       idConflict = true;
               }

               // If there is no conflict, create the new student
               if (!nameConflict && !idConflict)
               {
                   students.Add(new Student(txtAddID.Text, txtAddName.Text));
                   lstAbsent.Items.Clear();
                   foreach (Student s in students)
                       lstAbsent.Items.Add(s.getLVI_A());
                   WriteNewStudentFile();
                   txtAddName.Text = null;
                   txtAddID.Text = null;
               }
               else if (nameConflict && idConflict) // If both conflict
                   MessageBox.Show("The name and RFID are both already in use.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
               else if (!nameConflict && idConflict) // If ID conflicts
                   MessageBox.Show("The RFID is already in use.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
               else if (nameConflict && !idConflict) // If name conflicts
                   MessageBox.Show("The name is already in use.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

           }
           else if (prospName == "" && prospID == "") // If both are empty
               MessageBox.Show("Both the name and RFID fields are empty.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
           else if (prospName != "" && prospID == "") // If ID is empty
               MessageBox.Show("The RFID field is empty.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
           else if (prospName == "" && prospID != "") // If name is empty
               MessageBox.Show("The name field is empty.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
       }



       // Will contain code to save info to database
       private void btnSaveToDB_Click(object sender, EventArgs e)
       {
           //create new xls file
           string filename = Directory.GetCurrentDirectory() + "\\Spreadsheets\\" + DateTime.Now.Year.ToString() + "-" + DateTime.Now.Month.ToString() + "-" + DateTime.Now.Day.ToString() + ".xls";
           Workbook workbook;
           Console.WriteLine("creating file");
           workbook = new Workbook();


           Worksheet worksheet = new Worksheet(DateTime.Now.Year.ToString() + "-" + DateTime.Now.Month.ToString() + "-" + DateTime.Now.Day.ToString());
           worksheet.Cells[0, 0] = new Cell("Time In");
           worksheet.Cells[0, 1] = new Cell("Name");
           worksheet.Cells[0, 2] = new Cell("Time Out");
           worksheet.Cells.ColumnWidth[1] = 256 * 35;
           worksheet.Cells.ColumnWidth[0] = 256 * 10;
           worksheet.Cells.ColumnWidth[2] = 256 * 10;

           CellStyle headerstyle = new CellStyle();
           headerstyle.BackColor = Color.Yellow;

           worksheet.Cells[0, 0].Style = headerstyle;
           worksheet.Cells[0, 1].Style = headerstyle;
           worksheet.Cells[0, 2].Style = headerstyle;
           

           int ci = 0;
           foreach (Student s in students)
           {
               ci++;
               string inString = "ABS";
               string outString = "N/A";
               string ampmIn = "AM";
               string hourIn;
               string ampmOut = " AM";
               string hourOut;
               string minIn, minOut;

               if (s.getInTime() != Student.initTime)
               {
                   if (s.getInTime().Hour > 12)
                   {
                       hourIn = Convert.ToString(s.getInTime().Hour - 12);
                       ampmIn = " PM";
                   }
                   else
                       hourIn = Convert.ToString(s.getInTime().Hour);

                   minIn = Convert.ToString(s.getInTime().Minute);

                   if (hourIn.Length < 2)
                       hourIn.Insert(0, "0");
                   if (minIn.Length < 2)
                       minIn.Insert(0, "0");

                   inString = hourIn + ":" + minIn + ampmIn;
               }

               if (s.getOutTime() != Student.initTime)
               {
                   if (s.getOutTime().Hour > 12)
                   {
                       hourOut = Convert.ToString(s.getOutTime().Hour - 12);
                       ampmOut = " PM";
                   }
                   else
                       hourOut = Convert.ToString(s.getOutTime().Hour);

                   minOut = Convert.ToString(s.getInTime().Minute);

                   if (hourOut.Length < 2)
                       hourOut.Insert(0, "0");
                   if (minOut.Length < 2)
                       minOut.Insert(0, "0");

                   outString = hourOut + ":" + minOut + ampmOut;
               }

               worksheet.Cells[ci, 0] = new Cell(inString);
               worksheet.Cells[ci, 1] = new Cell(s.getName());
               worksheet.Cells[ci, 2] = new Cell(outString);
           }

           workbook.Worksheets.Add(worksheet);
           workbook.Save(filename);


           MessageBox.Show("Done!", "Save to Spreadsheet", MessageBoxButtons.OK, MessageBoxIcon.Information);
           
       }



       // Refreshes the absent, in, and out lists
       private void RefreshLists()
       {
           ClearLists();

           foreach (Student s in students)
           {
               bool absent = true;
               if (s.getInTime() != Student.initTime)
               {
                   AddLstItem(lstIn, s.getLVI_I());
                   absent = false;
               }

               if (s.getOutTime() != Student.initTime)
               {
                   AddLstItem(lstOut, s.getLVI_O());
                   absent = false;
               }

               if (absent)
                   AddLstItem(lstAbsent, s.getLVI_A());
           }
       }



       // Method for changing lblInfo's text, needed for thread safety.
       private void SetText_lblInfo(string text)
       {
           if (this.lblInfo.InvokeRequired)
           {
               SetTextCallback d = new SetTextCallback(SetText_lblInfo);
               this.Invoke(d, new object[] { text });
           }
           else
           {
               this.lblInfo.Text = text;
           }
       }



       // Adds list items to lists in a thread-safe manner
       private void AddLstItem(ListView list, ListViewItem item)
       {
           if (list.InvokeRequired)
           {
               AddListItem d = new AddListItem(AddLstItem);
               this.Invoke(d, new object[] { list, item });
           }
           else
           {
               list.Items.Add(item);
           }
       }



       // Clears out all three lists in a thread-safe manner
       private void ClearLists()
       {
           if (lstAbsent.InvokeRequired)
           {
               ClearLists_d d = new ClearLists_d(ClearLists);
               this.Invoke(d, new object[] { });
           }
           else
           {
               lstAbsent.Items.Clear();
               lstIn.Items.Clear();
               lstOut.Items.Clear();
           }
       }



       // Starts the wait timer
       private void StartTimer()
       {
           if (InvokeRequired)
           {
               StartTimer_d d = new StartTimer_d(StartTimer);
               this.Invoke(d, new object[] { });
           }
           else
           {
               tmrWaiter.Enabled = true;
           }
       }



       // Timer tick, will hopefully fix bug mentioned in shouldWait declaration comment
       private void tmrWaiter_Tick(object sender, EventArgs e)
       {
           tmrWaiter.Enabled = false;
           shouldWait = false;
           Console.WriteLine("timer stopping");
           waitloop = 0;
           Clear();
       }


   }
}
